package com.musollo.mp3tools.jaudiotagger;

import org.apache.log4j.Logger;
import org.jaudiotagger.audio.exceptions.CannotWriteException;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.FieldDataInvalidException;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.KeyNotFoundException;
import org.jaudiotagger.tag.id3.AbstractID3v2Frame;
import org.jaudiotagger.tag.id3.AbstractID3v2Tag;
import org.jaudiotagger.tag.id3.AbstractTagFrameBody;
import org.jaudiotagger.tag.id3.ID3v1Tag;
import org.jaudiotagger.tag.id3.ID3v24Frame;
import org.jaudiotagger.tag.id3.ID3v24Frames;
import org.jaudiotagger.tag.id3.ID3v24Tag;
import org.jaudiotagger.tag.id3.framebody.AbstractFrameBodyTextInfo;
import org.jaudiotagger.tag.id3.framebody.FrameBodyCOMM;
import org.jaudiotagger.tag.id3.framebody.FrameBodyTRCK;
import org.jaudiotagger.tag.id3.framebody.FrameBodyTYER;

import com.musollo.domain.MP3;
import com.musollo.exceptions.ParseException;
import com.musollo.mp3tools.TagWriter;

/**
 * This class implements the ITagWriter interface and uses the JAudioTagger
 * library to write MP3 tags.
 * 
 * @see TagWriter
 * @author paw.janssen
 * 
 */
public class JAudioTaggerWriter implements TagWriter {
	private static final Logger LOGGER = Logger.getLogger(JAudioTaggerWriter.class);
	private MP3 mp3FileToSave;
	private MP3File audioTaggerFile;

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void clearMP3Tags(final MP3 mp3) {
		this.mp3FileToSave = mp3;

		this.clearTags();

		mp3.setAlbum(null);
		mp3.setAlbumTrackNumber(0);
		mp3.setArtist(null);
		mp3.setComment(null);
		mp3.setGenre(null);
		mp3.setPuid(null);
		mp3.setTitle(null);
		mp3.setDate(null);
	}

	private void clearTags() {
		try {
			audioTaggerFile = JAudioTaggerReader.parseFile(this.mp3FileToSave.getMp3File());

			audioTaggerFile.setID3v1Tag(new ID3v1Tag());
			audioTaggerFile.setID3v2Tag(new ID3v24Tag());

			audioTaggerFile.commit();

		} catch (CannotWriteException e) {
			LOGGER.error("Tag Exception occurred with file " + this.mp3FileToSave.getMp3File(), e);
		} catch (ParseException e) {
			LOGGER.error("Could not read audioTaggerFile to clear tags " + this.mp3FileToSave.getMp3File(), e);
		}
	}

	private String getFrameIDFromFieldKey(final FieldKey key) {
		return ID3v24Frames.getInstanceOf().getId3KeyFromGenericKey(key).getFrameId();
	}

	/**
	 * Updates the actual file on the harddisk with the song information
	 * contained in this file.
	 * 
	 * @throws ParseException
	 * 
	 */
	private void save() {
		LOGGER.debug("Saving tag");

		try {
			audioTaggerFile = JAudioTaggerReader.parseFile(this.mp3FileToSave.getMp3File());

			this.clearTags();

			this.setAudioField(FieldKey.ARTIST, this.mp3FileToSave.getArtist());
			this.setAudioField(FieldKey.ALBUM, this.mp3FileToSave.getAlbum());
			this.setAudioField(FieldKey.TRACK, String.valueOf(this.mp3FileToSave.getAlbumTrackNumber()));
			this.setAudioField(FieldKey.GENRE, this.mp3FileToSave.getGenre());
			this.setAudioField(FieldKey.TITLE, this.mp3FileToSave.getTitle());
			this.setAudioField(FieldKey.YEAR, String.valueOf(this.mp3FileToSave.getDate()));
			this.setAudioField(FieldKey.COMMENT, this.mp3FileToSave.getComment());

			audioTaggerFile.commit();

		} catch (CannotWriteException e) {
			LOGGER.error("Tag Exception occurred with file " + this.mp3FileToSave.getMp3File(), e);
		} catch (ParseException e) {
			LOGGER.error("Could not read audioTaggerFile to clear tags " + this.mp3FileToSave.getMp3File(), e);
		}
	}

	private void setAudioField(final FieldKey key, final String value) {
		if (audioTaggerFile != null && value != null) {
			this.setID3v1AudioField(key, value);

			this.setID3v2AudioField(this.getFrameIDFromFieldKey(key), value);
		} else {
			LOGGER.error("Cannot save MP3 file metadata when audioFile does not exists, or when value is null");
		}
	}

	private void setID3v1AudioField(final FieldKey key, final String value) {
		ID3v1Tag tag = this.audioTaggerFile.getID3v1Tag();
		try {
			tag.setField(key, value);
			this.audioTaggerFile.setID3v1Tag(tag);
		} catch (KeyNotFoundException e) {
			LOGGER.error("Key not found exception occurred", e);
		} catch (FieldDataInvalidException e) {
			LOGGER.error("Field data invalid exception occurred", e);
		}
	}

	private void setID3v2AudioField(final String frameID, final String value) {
		AbstractID3v2Tag tag = this.audioTaggerFile.getID3v2Tag();

		AbstractID3v2Frame frame = tag.getFirstField(frameID);

		if (frame == null) {
			frame = new ID3v24Frame(frameID);
		}

		AbstractTagFrameBody body = frame.getBody();

		if (body instanceof AbstractFrameBodyTextInfo) {
			((AbstractFrameBodyTextInfo) frame.getBody()).setText(value);
			LOGGER.debug("Setting text frame body");
		} else if (body instanceof FrameBodyTRCK) {
			((FrameBodyTRCK) frame.getBody()).setTrackNo(value);
			LOGGER.debug("Setting tracknumber frame body");
		} else if (body instanceof FrameBodyCOMM) {
			((FrameBodyCOMM) frame.getBody()).setText(value);
			LOGGER.debug("Setting comment frame body");
		} else if (body instanceof FrameBodyTYER) {
			((FrameBodyTYER) frame.getBody()).setText(value);
			LOGGER.debug("Setting year frame body");
		}

		frame.setBody(body);
		tag.setFrame(frame);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void writeMP3(final MP3 mp3) {
		this.mp3FileToSave = mp3;

		this.save();
	}

}
